package com.flow.framework.lock.helper;

import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.lock.enumeration.ILockKeyEnum;
import com.flow.framework.lock.pojo.bo.LockKeyCacheBo;
import com.flow.framework.lock.pojo.bo.PreviousVersionLockBo;
import com.flow.framework.lock.service.lock.ILockService;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;
import java.util.List;

/**
 * 分布式锁辅助类：
 * 1、使用锁辅助类不可和@Transactional共用，避免锁已经释放，但是事务还未提交的情况，如果需要使用事务，请使用TransactionHelper，
 * 即获取锁完成之后再使用TransactionHelper开启事务并执行业务逻辑
 * 2、当前不支持传递释放锁时间，从业务角度出发，锁释放时间评估相对困难，所以目前采用redisson自动续期方案（Watch Dog）
 * 首次默认锁30秒，后面每10秒将续期一次，续期逻辑为将锁的超时时间重置为30秒
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/3/13
 */
@Slf4j
public class LockHelper {
    private static final String LOCK_CONNECTOR = ":";

    private static ILockService fastLockService;

    private static String applicationName;

    static void setFastLockService(ILockService fastLockService) {
        LockHelper.fastLockService = fastLockService;
    }

    @Deprecated
    public static ILockService getLockService() {
        return LockHelper.fastLockService;
    }

    static void setApplicationName(String applicationName) {
        LockHelper.applicationName = applicationName;
    }

    /**
     * 获取分布式锁并执行回调
     *
     * @param previousVersionLockBo 上一个版本的锁信息和参数
     * @param lockKeyEnum           锁枚举类
     * @param lockParams            锁参数
     * @param lockVoidCallback      无返回值的回调对象
     */
    public static void voidLockExecute(@Nullable PreviousVersionLockBo previousVersionLockBo, ILockKeyEnum lockKeyEnum,
                                       @Nullable List<String> lockParams, ILockVoidCallback lockVoidCallback) {
        voidLockExecute(previousVersionLockBo, lockKeyEnum, lockParams, 1500, lockVoidCallback);
    }

    /**
     * 获取分布式锁并执行回调
     *
     * @param previousVersionLockBo 上一个版本的锁信息和参数
     * @param lockKeyEnum           锁枚举类
     * @param waitTime              获取锁等待时间（毫秒）
     * @param lockParams            锁参数
     * @param lockVoidCallback      无返回值的回调对象
     */
    public static void voidLockExecute(@Nullable PreviousVersionLockBo previousVersionLockBo, ILockKeyEnum lockKeyEnum,
                                       @Nullable List<String> lockParams, long waitTime, ILockVoidCallback lockVoidCallback) {
        if (VerifyUtil.hasEmpty(lockKeyEnum, lockVoidCallback)) {
            log.error("lock param are empty.");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }

        LockKeyCacheBo lockKeyCacheBo = new LockKeyCacheBo();
        try {
            lock(previousVersionLockBo, lockKeyEnum, lockParams, waitTime, lockKeyCacheBo);
            lockVoidCallback.callback();
        } finally {
            unLock(lockKeyCacheBo);
        }
    }

    /**
     * 获取分布式锁并执行回调
     *
     * @param previousVersionLockBo 上一个版本的锁信息和参数
     * @param lockKeyEnum           锁枚举类
     * @param lockParams            锁参数
     * @param lockResultCallback    有返回值的回调对象
     * @param <T>                   直接结果参数
     * @return 执行结果
     */
    public static <T> T resultLockExecute(@Nullable PreviousVersionLockBo previousVersionLockBo, ILockKeyEnum lockKeyEnum,
                                          @Nullable List<String> lockParams, ILockResultCallback<T> lockResultCallback) {
        return resultLockExecute(previousVersionLockBo, lockKeyEnum, lockParams, 1500, lockResultCallback);
    }

    /**
     * 获取分布式锁并执行回调
     *
     * @param previousVersionLockBo 上一个版本的锁信息和参数
     * @param lockKeyEnum           锁枚举类
     * @param lockParams            锁参数
     * @param waitTime              获取锁等待时间（毫秒）
     * @param lockResultCallback    有返回值的回调对象
     * @param <T>                   直接结果参数
     * @return 执行结果
     */
    public static <T> T resultLockExecute(@Nullable PreviousVersionLockBo previousVersionLockBo, ILockKeyEnum lockKeyEnum,
                                          @Nullable List<String> lockParams, long waitTime, ILockResultCallback<T> lockResultCallback) {
        if (VerifyUtil.hasEmpty(lockKeyEnum, lockResultCallback)) {
            log.error("lock param are empty.");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }

        LockKeyCacheBo lockKeyCacheBo = new LockKeyCacheBo();
        try {
            lock(previousVersionLockBo, lockKeyEnum, lockParams, waitTime, lockKeyCacheBo);
            return lockResultCallback.callback();
        } finally {
            unLock(lockKeyCacheBo);
        }
    }

    private static void unLock(LockKeyCacheBo lockKeyCacheBo) {
        String currentVersionLockKey = lockKeyCacheBo.getCurrentVersionLockKey();
        if (!VerifyUtil.isEmpty(currentVersionLockKey) && lockKeyCacheBo.isCurrentVersionLockSuccess()) {
            fastLockService.unlock(currentVersionLockKey);
        }

        String previousVersionLockKey = lockKeyCacheBo.getPreviousVersionLockKey();
        if (!VerifyUtil.isEmpty(previousVersionLockKey) && lockKeyCacheBo.isPreviousVersionLockSuccess()) {
            fastLockService.unlock(previousVersionLockKey);
        }
    }

    private static void lock(PreviousVersionLockBo previousVersionLockBo, ILockKeyEnum lockKeyEnum,
                             List<String> lockParams, long waitTime, LockKeyCacheBo lockKeyCacheBo) {
        int lockKeyParamsSize = lockKeyEnum.getParamsSize();
        if (lockKeyParamsSize > 0) {
            if (VerifyUtil.isEmpty(lockParams)) {
                log.error("lock key params size mismatch actual lock params.");
                throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
            }
            if (lockKeyParamsSize != lockParams.size()) {
                log.error("lock key params size mismatch actual lock params.");
                throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
            }
        } else {
            if (!VerifyUtil.isEmpty(lockParams)) {
                log.error("lock key params size mismatch actual lock params.");
                throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
            }
        }

        // 获取上一个版本的锁
        String previousVersionLockKey = null;
        if (null != previousVersionLockBo) {
            previousVersionLockKey = getLockKey(previousVersionLockBo);
            lockKeyCacheBo.setPreviousVersionLockKey(previousVersionLockKey);
        }
        if (!VerifyUtil.isEmpty(previousVersionLockKey)) {
            boolean previousVersionLock = fastLockService.tryLock(previousVersionLockKey, waitTime, -1);
            lockKeyCacheBo.setPreviousVersionLockSuccess(previousVersionLock);
            if (!previousVersionLock) {
                log.error("get previous version lock error. lock key: {}", previousVersionLockKey);
                throw new CheckedException(SystemErrorCode.LOCK_ERROR);
            }
        }

        // 获取当前版本的锁
        String currentVersionLockKey = getLockKey(applicationName, lockKeyEnum, lockParams);
        lockKeyCacheBo.setCurrentVersionLockKey(currentVersionLockKey);
        boolean lock = fastLockService.tryLock(currentVersionLockKey, waitTime, -1);
        lockKeyCacheBo.setCurrentVersionLockSuccess(lock);
        if (!lock) {
            log.error("get lock error. lock key: {}", currentVersionLockKey);
            throw new CheckedException(SystemErrorCode.LOCK_ERROR);
        }
    }

    private static String getLockKey(PreviousVersionLockBo previousVersionLockBo) {
        String appName = previousVersionLockBo.getAppName();
        ILockKeyEnum lockKeyEnum = previousVersionLockBo.getLockKeyEnum();
        List<String> lockParams = previousVersionLockBo.getLockParams();
        return getLockKey(appName, lockKeyEnum, lockParams);
    }

    public static String getLockKey(ILockKeyEnum lockKeyEnum, List<String> lockParams) {
        return getLockKey(applicationName, lockKeyEnum, lockParams);
    }

    private static String getLockKey(String appName, ILockKeyEnum lockKeyEnum, List<String> lockParams) {
        if (!lockKeyEnum.getClass().isEnum()) {
            log.error("lock key isn't enum.");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }
        StringBuilder tempLockKey = new StringBuilder(appName + LOCK_CONNECTOR + lockKeyEnum.getClass().getName()
                + LOCK_CONNECTOR + ((Enum) lockKeyEnum).name());
        if (!VerifyUtil.isEmpty(lockParams)) {
            for (String lockParam : lockParams) {
                tempLockKey.append(LOCK_CONNECTOR).append(lockParam);
            }
        }
        return tempLockKey.toString();
    }


    public interface ILockVoidCallback {
        /**
         * 回调方法
         */
        void callback();
    }

    public interface ILockResultCallback<T> {
        /**
         * 回调方法
         *
         * @return 执行结果
         */
        T callback();
    }
}
